/*
 * Copyright 2012 Alain Pannetrat (L1L1@gmx.com)
 * 
 * This file is part of cookie-miner.
 * 
 * cookie-miner is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version.
 * 
 * cookie-miner is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License 
 * along with cookie-miner. If not, see http://www.gnu.org/licenses/.
 *
 * @version: $Id$
 *
 */
#include "http_server.h"
#include "log_facility.h"
#include "http_packet.h"
#include "bufio.h"
#include "effective_tld.h"
#include "http_server.h"
#include <pthread.h>
#include <string>
#include <cstdlib>
#include <vector>
#include "json.h"
#include <list>

unsigned short OPT_PORT = 9998;
bool OPT_COOKIES_ONLY = true;
bool OPT_DEMO_MODE = false;
unsigned OPT_DEMO_MAX = 2;

bool get_cookie(std::string& name, std::string& value, std::string &cookies)
{
	std::string part;
	size_t pos;

	if (cookies.size()==0) return false;

	if ((pos = cookies.find_first_of("; "))==cookies.npos)
	{
		part = cookies;
		cookies = "";
	}
	else
	{
		part = cookies.substr(0,pos);
		cookies = cookies.substr(pos+2);
	}

	if ((pos = part.find('='))==part.npos) return false;
	name = part.substr(0,pos);
	value = part.substr(pos+1);
	return true;
}

std::string isolate_domain(const std::string &url)
{
	size_t start = 0;
	size_t len = std::string::npos;

	if (url.find("http://")==0)
	{
		start = 7;
		len = url.find('/',start);
		if (len!=std::string::npos)
			len-=start;
	}
	else if (url.find("https://")==0)
	{
		start = 8;
		len = url.find('/',start);
		if (len!=std::string::npos)
			len-=start;
	}
	//logger.message(logger.LOG_DEBUG,"%u,%u,<%s>",start,len,url.c_str());
	return url.substr(start,len);
}

class PacketSummary {
	public:
		PacketSummary(int apacket_id=0, const std::string& ahost="", const std::string& aref="", const std::string& acookies="") :
			packet_id(apacket_id), 
			host(ahost), 
			referer(aref), 
			cookies(acookies) {}
		int packet_id;
		std::string host;
		std::string referer;
		std::string cookies;
	private:
		/* void */	
};

std::list<PacketSummary> history;

bool history_find(int id, PacketSummary& item)
{
	std::list<PacketSummary>::iterator it;

	for (it=history.begin();it!=history.end();it++)
	{
		if ((*it).packet_id==id)
		{
			item = *it;
			return true;
		}
	}
	return false;
}

void history_add(const PacketSummary& item)
{
	history.push_front(item);
	if (history.size()>100) history.pop_back();
}

typedef struct {
	BufferInOutSocket *io;
	std::vector<PacketSummary> graph_items;
	Dictionary<int> dups;
	int last_packet;
} context_t;


bool process_graph_items(context_t *ctx)
{
	std::string referer;
	std::string host;
	// std::string cookie_name;
	// std::string cookie_value;
	Json::Value *json = Json::create_object();
	bool in_tpl;
	bool tracking;
		
	json->set("message","graph");

	for (unsigned i=0;i<ctx->graph_items.size();i++)
	{
		Json::Value *jobj = Json::create_object();

		
		logger.message(logger.LOG_DEBUG,"processing(%i) %s -> %s",
			        ctx->graph_items[i].packet_id,
				ctx->graph_items[i].referer.c_str(),
				ctx->graph_items[i].host.c_str());
		
		tracking = false;

		in_tpl = tpl_match(ctx->graph_items[i].host);
		
		host = effective_domain(ctx->graph_items[i].host);

		if (ctx->graph_items[i].referer.size()!=0)
			referer = effective_domain(ctx->graph_items[i].referer);
		else
			referer = "";

		jobj->set("packet_id",ctx->graph_items[i].packet_id);

		if (referer=="" || referer==host)
		{
			if (!ctx->dups.exists(host))
			{
				jobj->set("type","vertex");
				jobj->set("source",host);
				if (ctx->graph_items[i].cookies.size()>0)
				{
					jobj->set("cookies",true); // ctx->graph_items[i].cookies;
				}
				if (tpl_match(host))
				{
					jobj->array("color")->push_back(0.8);
					jobj->array("color")->push_back(0);
					jobj->array("color")->push_back(0);
					tracking = true;
				}
				if (!OPT_COOKIES_ONLY || (tracking && OPT_COOKIES_ONLY)) 
				{
					json->array("items")->push_back(jobj);
				}
				ctx->dups.set(host,1);
			}
		}
		else
		{
			jobj->set("type","edge");
			jobj->set("source",referer);
			jobj->set("destination",host);

			if (ctx->graph_items[i].cookies.size()>0)
			{
				jobj->array("color")->push_back(0.8);
				jobj->array("color")->push_back(0);
				jobj->array("color")->push_back(0.8);
				tracking = true;

				/*
				   while (get_cookie(cookie_name,cookie_value,ctx->graph_items[i].cookies))
				   {
				   jobj["cookies"][cookie_name]=cookie_value;
				   }
				   */
				jobj->set("cookies",true);
			}	
			else
			{
				jobj->array("color")->push_back(0);
				jobj->array("color")->push_back(0);
				jobj->array("color")->push_back(0.8);
			}
				
			if (tpl_match(host))
			{
				Json::Value* extra = Json::create_object();

				extra->set("packet_id",ctx->graph_items[i].packet_id);
				extra->set("type","vertex");
				extra->set("source",host);
				extra->array("color")->push_back(1.0);
				extra->array("color")->push_back(0);
				extra->array("color")->push_back(0);
					
				json->array("items")->push_back(extra);

				//delete extra;
			}
			
			if (!OPT_COOKIES_ONLY || (tracking && OPT_COOKIES_ONLY)) 
			{
				json->array("items")->push_back(jobj);
			}
			
		}
		//delete jobj;
	}
	json->serialize(ctx->io);
	ctx->io->write_line("\r\n");
	ctx->io->write_flush();
	ctx->graph_items.clear();
	Json::unreference(json);
	return true;
}

void demo_mode(context_t *ctx)
{
	PacketSummary gi;
	unsigned i,j;
	char src[12];
	char dst[12];

	if (ctx->last_packet>0) return;

	gi.cookies = "yes";
	for (i=0;i<OPT_DEMO_MAX;i++)
	{
		for (j=0;j<OPT_DEMO_MAX;j++)
		{
			gi.packet_id=i*OPT_DEMO_MAX+j;
			sprintf(src,"%i",i*OPT_DEMO_MAX+j);
			sprintf(dst,"%i",i*OPT_DEMO_MAX+j+1);
			gi.referer = src;
			gi.host = dst;
			if (j<OPT_DEMO_MAX-1) ctx->graph_items.push_back(gi);
			sprintf(dst,"%i",(i+1)*OPT_DEMO_MAX+j);
			gi.host = dst;
			if (i<OPT_DEMO_MAX-1) ctx->graph_items.push_back(gi);

			sprintf(dst,"%i",(i+1)*OPT_DEMO_MAX+j+1);
			gi.host = dst;
			if (i<OPT_DEMO_MAX-1 && j<OPT_DEMO_MAX-1) ctx->graph_items.push_back(gi);
			sprintf(dst,"%i",(i-1)*OPT_DEMO_MAX+j+1);
			gi.host = dst;
			if (i>0 && j<OPT_DEMO_MAX-1) ctx->graph_items.push_back(gi);


		}
	}

	ctx->last_packet = gi.packet_id;
}


bool process_packet(HttpPacket *packet, void *params)
{
	std::string headline;
	context_t *ctx = (context_t *)params; 
	PacketSummary gi;

	//logger.message(logger.LOG_DEBUG,"parsing packet %i",packet->id());

	headline = packet->headline().full();

	ctx->last_packet = packet->id();

	if (headline[0]=='H') // is this a response packet ?
	{
		if (packet->headers().exists("Set-Cookie") && history_find(packet->parent_id(),gi))
		{
			// FIXME: capture all set-cookie(s) not just one
			gi.packet_id = packet->id();
			gi.cookies   = packet->headers().get("Set-Cookie");
			ctx->graph_items.push_back(gi);
		}	      
		return true;
	}

	gi.packet_id = packet->id();
	gi.host = packet->headers().get("Host");
	gi.host = isolate_domain(gi.host);

	if (packet->headers().exists("Referer"))
	{
		gi.referer = packet->headers().get("Referer");
		gi.referer = isolate_domain(gi.referer);
	}
	else
		gi.referer = "";

	if (packet->headers().exists("Cookie"))
	{
		gi.cookies = packet->headers().get("Cookie");
	}
	else
	{
		gi.cookies = "";
		if (gi.referer.size()>0) history_add(gi); // for check on return
	}
	history_add(gi);
	ctx->graph_items.push_back(gi);

/*
 * if (gi.destination!=gi.source)
		{
			if (packet->header_field_exists("Cookie"))
			{
				//
				gi.color=1;
			}
			else 
			{
				history_add(gi.id,gi.source,gi.destination);
				gi.color=0;
			}
			ctx->graph_items.push_back(gi);	
			
			logger.message(logger.LOG_DEBUG,"Here");

			if (tpl_match(gi.source))
			{
				gi.destination="";
				gi.color =2;
				ctx->graph_items.push_back(gi);
			}
		}
		else if (!ctx->dups.exists(gi.source))
		{
			ctx->dups.set(host,1);
			gi.destination="";
			if (tpl_match(gi.source))
				gi.color = 2;
			else
				gi.color = 0;
			ctx->graph_items.push_back(gi);	
		}
	}
	else if (!ctx->dups.exists(gi.source))
	{
		ctx->dups.set(host,1);
		gi.destination="";
		if (tpl_match(gi.source))
			gi.color = 2;
		else
			gi.color = 0;
		ctx->graph_items.push_back(gi);	
	}*/

	return true;
}

void *process_connection_thread(void *sock_ptr)
{
        int *sock = (int *)sock_ptr;
	context_t ctx;
	std::string req;
	int index;
	char filter[100];
	HttpPacketDB db;

	logger.new_thread();
	
	ctx.io=BufferInOutSocket::create(*sock);
	ctx.last_packet = -1;

	logger.message(logger.LOG_DEBUG,"Got connection on port 9998");
	//logger.verbosity(logger.LOG_ERROR);
	while (ctx.io->read_line(req))
	{
		trim_eol(req);
		//logger.message(logger.LOG_DEBUG,"Got request: <%s>",req.c_str());
		if (req=="quit") break;
		if (req=="top")
		{
			Json::Value* json = Json::create_object();

			db.open(true);
			ctx.last_packet = db.top_id();		
			db.close();
			json->set("message","top");
			json->set("top",ctx.last_packet);
			json->serialize(ctx.io);
			ctx.io->write_line("\r\n");
			ctx.io->write_flush();
			
			Json::unreference(json);
			continue;
		}
		if (req=="more" || (req[0]>='0' && req[0]<='9'))
		{
			ctx.dups.clear();
			if (req=="more")
			{
				index = ctx.last_packet+1;	
			}
			else
			{
				index = atol(req.c_str());
			}
			sprintf(filter,"packet_id>='%i' LIMIT 1000",index);
			if (!OPT_DEMO_MODE)
			{
				if (db.open(true))
				{
					db.iterate(filter,process_packet,&ctx,HttpPacketDB::ITERATE_HEADER_ONLY);
					db.close();
					process_graph_items(&ctx);
				}
				else
				{
					Json::Value* json = Json::create_object();

					json->set("message","error");
					json->set("details","could not open database");
					json->serialize(ctx.io);
					ctx.io->write_line("\r\n");
		
					Json::unreference(json);	
				}
			}
			else
			{
				demo_mode(&ctx);
				process_graph_items(&ctx);	
			}
		}
		else
		{
			Json::Value* json = Json::create_object();
			
			json->set("message","error");
			json->set("details","unrecognized command");
			json->serialize(ctx.io);
			ctx.io->write_line("\r\n");

			Json::unreference(json);
		}
		ctx.io->write_flush();
	}

	logger.message(logger.LOG_DEBUG,"Ended connection on port 9998");
	delete ctx.io;
	tcp_close(*sock);
	delete sock;
	pthread_exit(NULL);
        return NULL;
}

class MonitorServer: public HttpServer {
	public:
		MonitorServer(unsigned short port) : HttpServer(port) {}
		virtual bool do_get(BufferInOutSocket& bio, const HttpPacket& request);
	private:
		/* void */
};

bool MonitorServer::do_get(BufferInOutSocket& bio, const HttpPacket& request)
{
	URL url(request.headline().part(1));
	HttpPacket response;

	response.headline().set("HTTP/1.1 200 OK\r\n");
	response.headers().set("Content-type","text/plain");
	response.content_add(url.encode_relative());
	response.headers().set("Content-length",response.content_length());


	if (!response.headline().write_out(&bio) ||
	    !response.headers().write_out(&bio) ||
	    !response.content_write_out(&bio) ||
	    !bio.write_flush())
	{
		logger.message(logger.LOG_ERROR,"Failed to send response packet to client");
		return false;
	}
	return true;
}

int main(int argc, char **argv)
{
	/*
	int tpl_cnt;
	int opt;
	int sock;
	int *s;
        pthread_t thread_id;
	char ip[20];

	while ((opt = getopt(argc, argv, "ad:p:t:")) != -1) {
        	switch (opt) {
			case 'a':
				OPT_COOKIES_ONLY = false;
			        break;	
			case 'd':
				OPT_DEMO_MODE = true;
				OPT_DEMO_MAX = (unsigned)atoi(optarg);
				break;
			case 'p':
				OPT_PORT = (unsigned short)atoi(optarg);
				break;
			case 't':
				if ((tpl_cnt=tpl_load(optarg))<0)
					logger.message(logger.LOG_ERROR,"Could not load tpl file %s",optarg);
				else
					logger.message(logger.LOG_DEBUG,"Loaded %i names from tpl file %s",tpl_cnt,optarg);
				break;
		}
	}

	logger.message(logger.LOG_DEBUG,"Started monitor on port %u",OPT_PORT);

	sock = tcp_listen(OPT_PORT);
	
	for (;;)
	{
		s = new int;
		*s = tcp_accept(sock,ip);
		pthread_create(&thread_id,NULL,&process_connection_thread,(void *)s);
                pthread_detach(thread_id); 
	}
	return 0;
	*/
	MonitorServer server(9998);

	server.run();
}
